/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System Driver Module                                  */
/*                                                                           */
/*    Copyright (C) 2005 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    drvTouchScreen.c - Resistive Touch Screen Driver                       */
/*****************************************************************************/

#include <avr/io.h>
#include <avr/interrupt.h>
#include "sysSettings.h"
#include "drvTouchScreen.h"
#include "krnlMessageQueue.h"
#include "krnlMessages.h"


///////////////////////////////////////////////////////////////////////////////
// Const
#define drvTOUCH_AXIS_X 1
#define drvTOUCH_AXIS_Y 2

///////////////////////////////////////////////////////////////////////////////
// Module local variables
static dosByte l_current_axis;
static dosByte l_sample_counter;
static dosWord l_sample_sum;
static dosWord l_first_sample;
static dosWord l_prev_ad_x;
static dosWord l_prev_ad_y;
static dosWord l_ad_x;
static dosWord l_ad_y;

///////////////////////////////////////////////////////////////////////////////
// Module Local Functions 
void drvSelectAxis( dosByte in_axis );
void drvSendTouchMessage( dosWord in_message, dosWord in_ad_x, dosWord in_ad_y );

///////////////////////////////////////////////////////////////////////////////
// Initialize Touch Screen 
void guiInitPointer(void)
{
	l_current_axis = drvTOUCH_AXIS_X;
	l_sample_counter = 0;
	l_sample_sum = 0;
	l_prev_ad_x = 0xffff;
	l_prev_ad_y = 0xffff;

	// Init port
	drvSelectAxis( drvTOUCH_AXIS_X );

	// Init A/D converter
	ADMUX |= _BV(REFS1) | _BV(REFS0);
	ADCSRA = _BV(ADEN) | _BV(ADIE) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);
}

///////////////////////////////////////////////////////////////////////////////
// Select Axis to Measure
void drvSelectAxis( dosByte in_axis )
{
	// remove drive
	drvTOUCH_DRIVE_PORT &= ~(_BV(drvTOUCH_DRIVE_XR)|_BV(drvTOUCH_DRIVE_XL)|_BV(drvTOUCH_DRIVE_YT)|_BV(drvTOUCH_DRIVE_YB));
	drvTOUCH_DRIVE_DDR &= ~(_BV(drvTOUCH_DRIVE_XR)|_BV(drvTOUCH_DRIVE_XL)|_BV(drvTOUCH_DRIVE_YT)|_BV(drvTOUCH_DRIVE_YB));
	ADMUX &= drvTOUCH_INPUT_MASK;

	if( in_axis == drvTOUCH_AXIS_X )
	{
		// setup measuring Y axis
		drvTOUCH_DRIVE_DDR |= _BV(drvTOUCH_DRIVE_XR) | _BV(drvTOUCH_DRIVE_XL);
		drvTOUCH_DRIVE_PORT |= _BV(drvTOUCH_DRIVE_XR);
		ADMUX |= drvTOUCH_INPUT_Y;
	}
	else
	{
		// setup for measuring X axis
		drvTOUCH_DRIVE_DDR |= _BV(drvTOUCH_DRIVE_YT) | _BV(drvTOUCH_DRIVE_YB);
		drvTOUCH_DRIVE_PORT |= _BV(drvTOUCH_DRIVE_YB);
		ADMUX |= drvTOUCH_INPUT_X;
	}
}

///////////////////////////////////////////////////////////////////////////////
// Interrupt handler
SIGNAL(SIG_ADC)
{
	dosInt16 diff;
	dosWord sample;

	// throw away the first sample
	if( l_sample_counter > 0 )
	{
		// read sample
		sample = ADC;

		if( l_sample_counter == 1 )
			l_first_sample = sample;
		else
		{
			// calculate difference from the first sample
			diff = ((dosInt16)(l_first_sample)) - sample;

			if( diff < 0 )
				diff = -diff;

			if( diff > drvTOUCH_AD_VALUE_TRESHOLD )
				l_sample_sum = 0xffff;
		}

		// calculate sum
		if( l_sample_sum != 0xffff )
			l_sample_sum += ADC;

		// check for the required number of samples
		if( l_sample_counter == drvTOUCH_INPUT_AVERAGING )
		{
			// check if measurement was invalid (too big differeces between the measured A/D values)
			if( l_sample_sum == 0xffff )
			{
				// restart the measurement with X axis
				l_current_axis = drvTOUCH_AXIS_X;
			}
			else
			{
				// average
				l_sample_sum /= drvTOUCH_INPUT_AVERAGING;

				// calculate X coordinate
				if( l_current_axis == drvTOUCH_AXIS_X )
				{
					// not touched
					if( l_sample_sum < drvTOUCH_MIN_AD_X )
						l_ad_x = 0xffff;
					else
					{
						if( l_sample_sum > drvTOUCH_MAX_AD_X )
							l_ad_x = drvTOUCH_MAX_AD_X;
						else
							l_ad_x = l_sample_sum;
					}

					l_current_axis = drvTOUCH_AXIS_Y;
				}
				else
				{
					// not touched
					if( l_sample_sum < drvTOUCH_MIN_AD_Y )
						l_ad_y = 0xffff;
					else
					{
						if( l_sample_sum > drvTOUCH_MAX_AD_Y )
							l_ad_y = drvTOUCH_MAX_AD_Y;
						else
							l_ad_y = l_sample_sum;
					}

					l_current_axis = drvTOUCH_AXIS_X;

					// check not touched condition
					if( l_ad_x == 0xffff || l_ad_y == 0xffff )
						l_ad_x = l_ad_y = 0xffff;

					// if the touch was released
					if( l_ad_x == 0xffff && l_prev_ad_x != 0xffff )
					{
						drvSendTouchMessage( msgGUI_POINTERUP, l_prev_ad_x, l_prev_ad_y );
						l_prev_ad_x = l_ad_x;
						l_prev_ad_y = l_ad_y;
					}
					else
					{
						// touch was pressed
						if( l_prev_ad_x == 0xffff && l_ad_x != 0xffff )
						{
							drvSendTouchMessage( msgGUI_POINTERDOWN, l_ad_x, l_ad_y );
							l_prev_ad_x = l_ad_x;
							l_prev_ad_y = l_ad_y;
						}
						else
						{
							// if the touch is pressed
							if( l_ad_x != 0xffff )
							{
								// calculate the movement since the last measurement of the X axis
								diff = ((dosInt16)(l_ad_x)) - ((dosInt16)(l_prev_ad_x));
								if( diff < 0 )
									diff = -diff;

								// if the movement higher than the treshold -> send message
								if( diff > drvTOUCH_FOCUS_THRESHOLD )
								{
									drvSendTouchMessage( msgGUI_POINTERMOVE, l_ad_x, l_ad_y );
									l_prev_ad_x = l_ad_x;
									l_prev_ad_y = l_ad_y;
								}
								else
								{
									// calculate the movement since the last measurement of the Y axis
									diff = ((dosInt16)(l_ad_y)) - ((dosInt16)(l_prev_ad_y));
									if( diff < 0 )
										diff = -diff;

									// if the movement higher than the treshold -> send message
									if( diff > drvTOUCH_FOCUS_THRESHOLD )
									{
										drvSendTouchMessage( msgGUI_POINTERMOVE, l_ad_x, l_ad_y );
										l_prev_ad_x = l_ad_x;
										l_prev_ad_y = l_ad_y;
									}
								}
							}
						}
					}
				}
			}
			// prepare for the next measurement
			l_sample_sum = 0;
			l_sample_counter = 0;
			drvSelectAxis( l_current_axis );
		}
		else
			l_sample_counter++;
	}
	else
		l_sample_counter++;
}

///////////////////////////////////////////////////////////////////////////////
// Send pointer messages
void drvSendTouchMessage( dosWord in_message, dosWord in_ad_x, dosWord in_ad_y )
{
	krnlMessage* message;
	dosWord x;
	dosWord y;

	message = krnlAllocateAsyncMessage();

	if( message != DOS_NULL )
	{
		// calculate X coordinate
		x = ((in_ad_x - drvTOUCH_MIN_AD_X) * ((guiSCREEN_WIDTH * 128U)/(drvTOUCH_MAX_AD_X-drvTOUCH_MIN_AD_X))) / 128;
		
		// calculate Y coordinate
		y = ((in_ad_y - drvTOUCH_MIN_AD_Y) * ((guiSCREEN_HEIGHT * 128U)/(drvTOUCH_MAX_AD_Y-drvTOUCH_MIN_AD_Y))) / 128;

		// init message
		message->Id = in_message;

		message->Data[0] = LOW( x );
		message->Data[1] = HIGH( x );
		message->Data[2] = LOW( y );
		message->Data[3] = HIGH( y );
	}
}
